package main

import (
	crand "crypto/rand"
	"errors"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/event"
	"path/filepath"
	"sync"
)

const KeyStoreScheme = "keystore"

var (
	ErrDecrypt = errors.New("could not decrypt key with given password")
)

type Account struct {
	Address common.Address `json:"address"` // Ethereum account address derived from the key
	URL     URL            `json:"url"`     // Optional resource locator within a backend
}

type URL struct {
	Scheme string // Protocol scheme to identify a capable account backend
	Path   string // Path for the backend to identify a unique entity
}

type keystoreWallet struct {
	account  Account   // Single account contained in this wallet
	keystore *KeyStore // Keystore where the account originates from
}

// KeyStore manages a key storage directory on disk.
type KeyStore struct {
	storage keyStorePassphrase // Storage backend, might be cleartext or encrypted
	changes chan struct{}      // Channel receiving change notifications from the cache

	wallets     []keystoreWallet        // Wallet wrappers around the individual key files
	updateFeed  event.Feed              // Event feed to notify wallet additions/removals
	updateScope event.SubscriptionScope // Subscription scope tracking current live listeners
	updating    bool                    // Whether the event notification loop is running

	mu       sync.RWMutex
	importMu sync.Mutex // Import Mutex locks the import to prevent two insertions from racing
}

// NewKeyStore creates a keystore for the given directory.
func NewKeyStore(keydir string, scryptN, scryptP int) *KeyStore {
	keydir, _ = filepath.Abs(keydir)
	ks := &KeyStore{storage: keyStorePassphrase{keydir, scryptN, scryptP, false}}
	ks.init(keydir)
	return ks
}

func (ks *KeyStore) init(keydir string) {
	// Lock the mutex since the account cache might call back with events
	ks.mu.Lock()
	defer ks.mu.Unlock()

	// Create the initial list of wallets from the cache
	accs := scanAccounts(keydir)
	ks.wallets = make([]keystoreWallet, len(accs))
	for i := 0; i < len(accs); i++ {
		ks.wallets[i] = keystoreWallet{account: accs[i], keystore: ks}
	}
}

// NewAccount generates a new key and stores it into the key directory,
// encrypting it with the passphrase.
func (ks *KeyStore) NewAccount(passphrase string) (Account, error) {
	_, account, err := storeNewKey(ks.storage, crand.Reader, passphrase)
	if err != nil {
		return Account{}, err
	}
	return account, nil
}
